﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
/*
 * 代码已托管 https://gitee.com/dlgcy/dotnetcodes/
 * 版本：2023年07月09日
 */
namespace DotNet.Utilities
{
    public static class ClassHelper
    {
        #region 深拷贝

        //(前两个方法)原文链接：https://blog.csdn.net/HQ1356466973/article/details/83658756

        /// <summary>
        /// 深拷贝，通过序列化与反序列化实现（类需要标记为可序列化[Serializable]）
        /// </summary>
        /// <typeparam name="T">深拷贝的类类型</typeparam>
        /// <param name="obj">深拷贝的类对象</param>
        /// <returns></returns>
        public static T DeepCopyByBin<T>(T obj)
        {
            object retval;
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                //序列化成流
                bf.Serialize(ms, obj);

                //反序列化成对象
                ms.Seek(0, SeekOrigin.Begin);
                retval = bf.Deserialize(ms);
                ms.Close();
            }

            return (T)retval;
        }

        /// <summary>
        /// 深拷贝（字段），通过反射实现
        /// </summary>
        /// <typeparam name="T">深拷贝的对象类型</typeparam>
        /// <param name="obj">深拷贝的源对象</param>
        /// <returns>新的对象</returns>
        public static T DeepCopyFields<T>(T obj)
        {
            if (obj == null) return default(T);

            //如果是字符串或值类型则直接返回
            if (obj is string || obj.GetType().IsValueType) return obj;

            object result = Activator.CreateInstance(obj.GetType());

            FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
            foreach (FieldInfo field in fields)
            {
                try
                {
                    field.SetValue(result, DeepCopyFields(field.GetValue(obj)));
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Set field [{field.Name}] Exception：{ex}");
                }
            }

            return (T)result;
        }

        /// <summary>
        /// 深拷贝（属性），通过反射实现
        /// </summary>
        /// <typeparam name="T">深拷贝的对象类型</typeparam>
        /// <param name="obj">深拷贝的源对象</param>
        /// <returns>新的对象</returns>
        public static T DeepCopyProperties<T>(T obj)
        {
            if (obj == null) return default(T);

            //如果是字符串或值类型则直接返回
            if (obj is string || obj.GetType().IsValueType) return obj;

            object result = Activator.CreateInstance(obj.GetType());

            var properties = obj.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                try
                {
                    property.SetValue(result, DeepCopyProperties(property.GetValue(obj)));
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Set property [{property.Name}] Exception：{ex}");
                }
            }

            return (T)result;
        }

        #endregion

        #region 深拷贝 Ⅱ

        /// <summary>
        /// 【推荐】深拷贝：通过反射实现复杂对象的深拷贝（支持字典等类型）
        /// 修改自：https://www.cnblogs.com/seekdream/p/15193616.html
        /// </summary>
        /// <param name="sourceObj">源对象</param>
        /// <param name="propertiesBindingFlags">属性搜索方式标记（默认为公共属性）</param>
        /// <returns>深拷贝生成的对象</returns>
        public static T CloneObject<T>(T sourceObj, BindingFlags propertiesBindingFlags = BindingFlags.Public | BindingFlags.Instance)
        {
            if (sourceObj == null) return default(T);

            //Get the type of source object
            Type sourceType = sourceObj.GetType();

            //如果是字符串或值类型则直接返回
            if (sourceObj is string || sourceType.IsValueType) return sourceObj;

            //create a new instance of that type
            object targetObj = Activator.CreateInstance(sourceType);

            //支持直接的列表对象;
            if (typeof(IList).IsAssignableFrom(sourceType))
            {
                var list = (IList)sourceObj;
                foreach (var item in list)
                {
                    var copyObj = CloneObject(item);
                    (targetObj as IList)?.Add(copyObj);
                }

                return (T)targetObj;
            }

            //Get all the properties of source object type
            //PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            PropertyInfo[] propertyInfo = sourceType.GetProperties(propertiesBindingFlags);

            //Assign all source property to taget object 's properties
            foreach (PropertyInfo property in propertyInfo)
            {
                try
                {
                    //Check whether property can be written to
                    if (!property.CanWrite) continue;

                    /* The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single. */

                    //check whether property type is value type, enum or string type
                    if (property.PropertyType.IsPrimitive || property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType == typeof(string))
                    {
                        property.SetValue(targetObj, property.GetValue(sourceObj, null), null);
                    }
                    else if (property.PropertyType.GetConstructor(Type.EmptyTypes) == null)
                    {
                        //不支持没有无参公共构造函数的类型;
                        Debug.WriteLine($"Property [{property.Name}] (Type-[{property.PropertyType}]) is not provides a parameterless constructor.");
                        continue;
                    }
                    else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
                    {
                        MapEnumerableProperty(sourceObj, property, targetObj, property);
                    }
                    //else property type is object/complex types, so need to recursively call this method until the end of the tree is reached
                    else
                    {
                        object objPropertyValue = property.GetValue(sourceObj, null);
                        if (objPropertyValue == null)
                        {
                            property.SetValue(targetObj, null, null);
                        }
                        else if (objPropertyValue.GetType().IsPrimitive || objPropertyValue.GetType().IsEnum || objPropertyValue is string)
                        {
                            property.SetValue(targetObj, objPropertyValue, null);
                        }
                        else
                        {
                            property.SetValue(targetObj, CloneObject(objPropertyValue), null);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Handle property [{property.Name}] exception：{ex}");
                }
            }

            return (T)targetObj;
        }

        #endregion

        #region 共用方法

        /// <summary>
        /// 映射可枚举类型的属性（包括 IList、IDictionary、Array）
        /// </summary>
        /// <param name="sourceObj">源对象</param>
        /// <param name="sourcePropertyInfo">源对象的属性信息</param>
        /// <param name="targetObj">目标对象</param>
        /// <param name="targetPropertyInfo">目标对象的可枚举类型的属性信息</param>
        /// <example>
        /// <code>
        /// 附-判断是否是可枚举类型的属性：if(IsNeedMapEnumerable(targetProperty.PropertyType))
        /// </code>
        /// </example>
        private static void MapEnumerableProperty(object sourceObj, PropertyInfo sourcePropertyInfo, object targetObj, PropertyInfo targetPropertyInfo)
        {
            //Include List and Dictionary and......
            if (targetPropertyInfo.PropertyType.IsGenericType)
            {
                var cloneObj = Activator.CreateInstance(targetPropertyInfo.PropertyType);

                var addMethod = targetPropertyInfo.PropertyType.GetMethod("Add",
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

                Debug.Assert(null != addMethod);

                var currentValues = sourcePropertyInfo.GetValue(sourceObj, null) as IEnumerable;
                if (currentValues == null)
                {
                    targetPropertyInfo.SetValue(targetObj, null, null);
                }
                else
                {
                    if (cloneObj is IDictionary)
                    {
                        cloneObj = cloneObj as IDictionary;
                        foreach (var currentValue in currentValues)
                        {
                            var propInfoKey = currentValue.GetType().GetProperty("Key");
                            var propInfoValue = currentValue.GetType().GetProperty("Value");
                            if (propInfoKey != null && propInfoValue != null)
                            {
                                var keyValue = propInfoKey.GetValue(currentValue, null);
                                var valueValue = propInfoValue.GetValue(currentValue, null);

                                object finalKeyValue, finalValueValue;

                                //Get finalKeyValue
                                var currentKeyType = keyValue.GetType();
                                if (currentKeyType.IsPrimitive || currentKeyType.IsEnum || currentKeyType == typeof(string))
                                {
                                    finalKeyValue = keyValue;
                                }
                                else
                                {
                                    //Reference type
                                    var copyObj = CloneObject(keyValue);
                                    finalKeyValue = copyObj;
                                }

                                //Get finalValueValue
                                var currentValueType = valueValue.GetType();
                                if (currentValueType.IsPrimitive || currentValueType.IsEnum ||
                                    currentValueType == typeof(string))
                                {
                                    finalValueValue = valueValue;
                                }
                                else
                                {
                                    //Reference type
                                    var copyObj = CloneObject(valueValue);
                                    finalValueValue = copyObj;
                                }

                                addMethod.Invoke(cloneObj, new[] { finalKeyValue, finalValueValue });
                            }
                        }

                        targetPropertyInfo.SetValue(targetObj, cloneObj, null);
                    }
                    //Common IList type
                    else
                    {
                        foreach (var currentValue in currentValues)
                        {
                            var currentType = currentValue.GetType();
                            if (currentType.IsPrimitive || currentType.IsEnum || currentType == typeof(string))
                            {
                                addMethod.Invoke(cloneObj, new[] { currentValue });
                            }
                            else
                            {
                                //Reference type
                                var copyObj = CloneObject(currentValue);
                                addMethod.Invoke(cloneObj, new[] { copyObj });
                            }
                        }

                        targetPropertyInfo.SetValue(targetObj, cloneObj, null);
                    }
                }
            }
            //Array type
            else
            {
                var currentValues = sourcePropertyInfo.GetValue(sourceObj, null) as Array;
                if (null == currentValues)
                {
                    targetPropertyInfo.SetValue(targetObj, null, null);
                }
                else
                {
                    var cloneObj = Activator.CreateInstance(targetPropertyInfo.PropertyType, currentValues.Length) as Array;
                    var arrayList = new ArrayList();
                    for (var i = 0; i < currentValues.Length; i++)
                    {
                        var currentType = currentValues.GetValue(i).GetType();
                        if (currentType.IsPrimitive || currentType.IsEnum || currentType == typeof(string))
                        {
                            arrayList.Add(currentValues.GetValue(i));
                        }
                        else
                        {
                            //Reference type
                            var copyObj = CloneObject(currentValues.GetValue(i));
                            arrayList.Add(copyObj);
                        }
                    }

                    arrayList.CopyTo(cloneObj, 0);
                    targetPropertyInfo.SetValue(targetObj, cloneObj, null);
                }
            }
        }

        /// <summary>
        /// 是否需要当作可枚举类型(集合)来映射
        /// </summary>
        /// <param name="propertyType">属性的类型</param>
        /// <returns>是否</returns>
        public static bool IsNeedMapEnumerable(Type propertyType)
        {
            if (propertyType.IsPrimitive || propertyType.IsValueType || propertyType.IsEnum || propertyType == typeof(string))
            {
                return false;
            }

            if (propertyType.GetConstructor(Type.EmptyTypes) == null)
            {
                //不支持没有无参公共构造函数的类型;
                return false;
            }

            //此判断对于 string 等也是返回 true，所以需要上面的一系列过滤;
            if (typeof(IEnumerable).IsAssignableFrom(propertyType))
            {
                return true;
            }

            return false;
        }

        #endregion

        #region Mapper

        /// <summary>
        /// [dlgcy] 自动映射（将源对象的同名属性值赋给目标对象）
        /// </summary>
        /// <remarks>不能代替深拷贝来用</remarks>
        /// <typeparam name="TTarget">目标类型</typeparam>
        /// <typeparam name="TSource">源类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="ignoreNames">忽略的属性名列表</param>
        /// <returns>目标对象</returns>
        public static TTarget AutoMap<TTarget, TSource>(TSource source, List<string> ignoreNames = null)
        {
            TTarget target = Activator.CreateInstance<TTarget>();
            try
            {
                var sourceType = source.GetType();
                var targetType = typeof(TTarget);

                foreach (PropertyInfo targetProperty in targetType.GetProperties())
                {
                    try
                    {
                        if (ignoreNames?.Contains(targetProperty.Name) ?? false) continue;
                        if (!targetProperty.CanWrite) continue;

                        var sourceProperty = sourceType.GetProperty(targetProperty.Name);
                        if (sourceProperty != null)
                        {
                            if(sourceProperty.PropertyType != targetProperty.PropertyType) continue;

                            if (IsNeedMapEnumerable(targetProperty.PropertyType))
                            {
                                MapEnumerableProperty(source, sourceProperty, target, targetProperty);
                            }
                            else
                            {
                                targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            return target;
        }

        /// <summary>
        /// [dlgcy] 自动映射（将源对象的同名属性值赋给目标对象）
        /// </summary>
        /// <remarks>不能代替深拷贝来用</remarks>
        /// <typeparam name="TTarget">目标类型</typeparam>
        /// <typeparam name="TSource">源类型</typeparam>
        /// <param name="target">目标对象</param>
        /// <param name="source">源对象</param>
        /// <param name="ignoreNames">忽略的属性名列表</param>
        /// <returns>是否成功</returns>
        public static bool AutoMap<TTarget, TSource>(TTarget target, TSource source, List<string> ignoreNames = null)
        {
            bool result = true;
            try
            {
                var sourceType = source.GetType();
                var targetType = target.GetType();

                foreach (PropertyInfo targetProperty in targetType.GetProperties())
                {
                    try
                    {
                        if (ignoreNames?.Contains(targetProperty.Name) ?? false) continue;
                        if (!targetProperty.CanWrite) continue;

                        var sourceProperty = sourceType.GetProperty(targetProperty.Name);
                        if (sourceProperty != null)
                        {
                            if (sourceProperty.PropertyType != targetProperty.PropertyType) continue;

                            if (IsNeedMapEnumerable(targetProperty.PropertyType))
                            {
                                MapEnumerableProperty(source, sourceProperty, target, targetProperty);
                            }
                            else
                            {
                                targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex);
                        result = false;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                result = false;
            }

            return result;
        }

        #endregion

        #region 调用信息

        /* 参考：https://blog.csdn.net/m0_37886901/article/details/105266848 */

        /// <summary>
        /// 获取方法调用信息;
        /// </summary>
        /// <param name="index">0是本身，1是调用方，2是调用方的调用方...以此类推</param>
        /// <returns>MethodInfo 对象</returns>
        public static MethodInfo GetMethodInfo(int index)
        {
            try
            {
                index++; //由于这里是封装了方法，相当于上端想要获取本身，其实对于这里而言，上端的本身就是这里的上端，所以需要+1，以此类推
                var stack = new StackTrace(true);

                //0是本身，1是调用方，2是调用方的调用方...以此类推
                var currentFrame = stack.GetFrame(index);
                var method = currentFrame.GetMethod();
                var module = method.Module;
                var declaringType = method.DeclaringType;
                var stackFrames = stack.GetFrames();

                string callChain = string.Join(" -> ", stackFrames.Select((r, i) =>
                {
                    if (i == 0) return null;
                    var m = r.GetMethod();
                    return $"{m.DeclaringType.FullName}.{m.Name}";
                }).Where(r => !string.IsNullOrWhiteSpace(r)).Reverse());

                return new MethodInfo()
                {
                    Method = method,
                    ModuleName = module.Name,
                    Namespace = declaringType.Namespace,
                    ClassName = declaringType.Name,
                    FullClassName = declaringType.FullName,
                    MethodName = method.Name,
                    CallChain = callChain,
                    LineNumber = currentFrame.GetFileLineNumber(),
                    FileName = currentFrame.GetFileName(),
                };
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return null;
            }
        }

        /// <summary>
        /// 方法调用信息
        /// </summary>
        public class MethodInfo
        {
            /// <summary>
            /// 方法完整信息;
            /// </summary>
            public MethodBase Method { get; set; }

            /// <summary>
            /// 模块名
            /// </summary>
            public string ModuleName { get; set; }

            /// <summary>
            /// 命名空间
            /// </summary>
            public string Namespace { get; set; }

            /// <summary>
            /// 类名
            /// </summary>
            public string ClassName { get; set; }

            /// <summary>
            /// 完整类名
            /// </summary>
            public string FullClassName { get; set; }

            /// <summary>
            /// 方法名
            /// </summary>
            public string MethodName { get; set; }

            /// <summary>
            /// 调用链
            /// </summary>
            public string CallChain { get; set; }

            /// <summary>
            /// 行号
            /// </summary>
            public int LineNumber { get; set; }

            /// <summary>
            /// 文件名
            /// </summary>
            public string FileName { get; set; }
        }

        #endregion

        #region 序列化反序列化

        /// <summary>
        /// 对象序列化为字节数组（类需要标记为可序列化[Serializable]）
        /// </summary>
        /// <param name="obj">对象</param>
        /// <returns>字节数组</returns>
        public static byte[] SerializeToBytes(this object obj)
        {
            byte[] newArray = null;
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream())
            {
                binaryFormatter.Serialize(stream, obj);

                //newArray = new byte[stream.Length];
                //stream.Position = 0;
                //stream.Read(newArray, 0, (int) stream.Length);
                //stream.Close();

                stream.Seek(0, SeekOrigin.Begin);
                //newArray = stream.GetBuffer();
                newArray = stream.ToArray();
            }

            return newArray;
        }

        /// <summary>
        /// 字节数组反序列化为对象
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <returns>对象</returns>
        public static object DeserializeFromBytes(this byte[] bytes)
        {
            using MemoryStream stream = new MemoryStream(bytes);
            BinaryFormatter bf = new BinaryFormatter();
            object obj = bf.Deserialize(stream);
            stream.Close();
            return obj;
        }

        /// <summary>
        /// 字节数组反序列化为对象
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <returns>指定类型的对象，如果不匹配返回null</returns>
        public static T DeserializeFromBytes<T>(this byte[] bytes) where T : class
        {
            object obj = DeserializeFromBytes(bytes);
            return obj as T;
        }

        #endregion
    }
}
